package com.note.gateway.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.note.common.utils.JwtUtils;
import com.note.common.utils.RedisUtils;
import com.note.common.vo.ResponseVo;
import com.note.gateway.config.GatewayConfig;
import com.note.gateway.feign.UserFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import org.springframework.context.annotation.Lazy;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * 认证过滤器
 * 负责拦截请求，验证Token，放行符合条件的请求
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    @Autowired
    private GatewayConfig gatewayConfig;

    @Autowired
    private JwtUtils jwtUtils;

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    @Lazy
    private UserFeignClient userFeignClient;

    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    /**
     * Token在Header中的名称
     */
    private static final String HEADER_TOKEN = "Authorization";

    /**
     * Token前缀
     */
    private static final String TOKEN_PREFIX = "Bearer ";

    /**
     * 用户ID在Header中的名称
     */
    private static final String HEADER_USER_ID = "X-User-Id";

    /**
     * 用户名在Header中的名称
     */
    private static final String HEADER_USERNAME = "X-Username";

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().value();
        String method = request.getMethod().toString();

        log.info("🔑 AuthFilter拦截请求: {} {}", method, path);
        log.debug("🔍 请求原始URL: {}", request.getURI());

        // 获取请求是通过哪个路由转发的
        Route route = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (route != null) {
            log.debug("🔍 匹配路由: id={}, uri={}", route.getId(), route.getUri());
        } else {
            log.warn("⚠️ 未找到匹配的路由!");
        }

        // 记录路由目标地址
        URI routeUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        if (routeUri != null) {
            log.debug("🔍 路由目标: {}", routeUri);
        }

        // 1. 检查是否是白名单路径（不需要认证）
        if (isWhiteListPath(path)) {
            log.info("✅ 白名单路径，允许通过: {}", path);
            return chain.filter(exchange);
        }

        // 2. 检查是否是需要认证的路径
        if (!isAuthPath(path)) {
            // 如果不是白名单也不是需要认证的，默认需要认证
            log.info("🔒 默认需要认证的路径: {}", path);
        } else {
            log.info("🔒 配置要求认证的路径: {}", path);
        }

        // 3. 获取Token
        String token = getToken(request);
        if (!StringUtils.hasText(token)) {
            log.warn("❌ 未提供Token, 拒绝访问: {}", path);
            return unauthorized(exchange, "未提供Token");
        }

        // 4. 验证Token
        if (!jwtUtils.validateToken(token)) {
            log.warn("❌ Token无效, 拒绝访问: {}", path);
            return unauthorized(exchange, "Token无效");
        }

        // 5. 从Token中获取用户ID和用户名
        Long userId = jwtUtils.getUserIdFromToken(token);
        String username = jwtUtils.getUsernameFromToken(token);
        if (userId == null || !StringUtils.hasText(username)) {
            log.warn("❌ Token中未包含用户信息, 拒绝访问: {}", path);
            return unauthorized(exchange, "Token中未包含用户信息");
        }

        // 6. 验证Token是否在Redis中存在（检查是否已登出）
        if (validateTokenInRedis(userId, token)) {
            // Redis中存在有效Token或通过Feign验证通过
            log.debug("✅ Token验证通过: userId={}", userId);
        } else {
            // 验证失败，拒绝访问
            log.warn("❌ Token验证失败, 拒绝访问: userId={}, path={}", userId, path);
            return unauthorized(exchange, "登录已失效，请重新登录");
        }

        // 7. 将用户信息添加到请求头
        ServerHttpRequest newRequest = request.mutate()
                .header(HEADER_USER_ID, String.valueOf(userId))
                .header(HEADER_USERNAME, username)
                .build();

        log.info("✅ 认证成功，添加用户信息到请求: userId={}, username={}, path={}", userId, username, path);

        return chain.filter(exchange.mutate().request(newRequest).build())
                .doOnSuccess(v -> {
                    HttpStatusCode statusCode = exchange.getResponse().getStatusCode();
                    log.info("🏁 请求完成: {} {}, 状态码: {}", method, path, statusCode);
                })
                .doOnError(err -> {
                    log.error("❌ 请求处理错误: {} {}, 错误: {}", method, path, err.getMessage());
                });
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE;
    }

    /**
     * 检查是否是白名单路径（不需要认证）
     */
    private boolean isWhiteListPath(String path) {
        List<String> whiteListPatterns = gatewayConfig.getPatterns().getDont();
        boolean result = matchAnyPattern(path, whiteListPatterns);
        if (result) {
            log.debug("✓ 路径{}匹配白名单", path);
        }
        return result;
    }

    /**
     * 检查是否是需要认证的路径
     * 注意：如果不在白名单中，且不明确匹配认证路径，默认也需要认证
     */
    private boolean isAuthPath(String path) {
        List<String> authPatterns = gatewayConfig.getPatterns().getAdd();
        boolean result = matchAnyPattern(path, authPatterns);
        if (result) {
            log.debug("✓ 路径{}匹配认证规则", path);
        }
        return result;
    }

    /**
     * 匹配路径是否符合任意模式
     */
    private boolean matchAnyPattern(String path, List<String> patterns) {
        if (patterns == null || patterns.isEmpty()) {
            return false;
        }

        for (String pattern : patterns) {
            boolean matched = pathMatcher.match(pattern, path);
            if (matched) {
                log.debug("路径匹配: {} 与模式 {} 匹配成功", path, pattern);
                return true;
            }
        }

        log.debug("路径 {} 未匹配任何模式", path);
        return false;
    }

    /**
     * 从请求中获取Token
     */
    private String getToken(ServerHttpRequest request) {
        String header = request.getHeaders().getFirst(HEADER_TOKEN);
        if (!StringUtils.hasText(header)) {
            log.debug("🔍 请求头中未找到Authorization: {}", request.getURI());
            // 检查请求头
            HttpHeaders headers = request.getHeaders();
            log.debug("🔍 请求头详情: {}", headers);
            return null;
        }

        log.debug("🔍 获取到Authorization头: {}", header);

        if (!header.startsWith(TOKEN_PREFIX)) {
            log.debug("🔍 Authorization头不是Bearer格式: {}", header);
            return null;
        }

        return header.substring(TOKEN_PREFIX.length());
    }

    /**
     * 返回未授权响应
     */
    private Mono<Void> unauthorized(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        try {
            // 统一对外显示为登录过期信息
            String displayMessage = "登录已过期，请重新登录";
            // 仅在日志中保留实际错误原因
            log.info("❌ 认证失败，原因: {}, 返回信息: {}", message, displayMessage);

            ResponseVo<Object> responseVo = ResponseVo.error(HttpStatus.UNAUTHORIZED.value(), displayMessage);
            byte[] bytes = objectMapper.writeValueAsBytes(responseVo);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(buffer));
        } catch (Exception e) {
            log.error("返回未授权响应异常: error={}", e.getMessage(), e);

            // 发生异常时，返回简单的错误信息
            String errorJson = "{\"code\": 401, \"message\": \"未授权\"}";
            byte[] bytes = errorJson.getBytes(StandardCharsets.UTF_8);
            DataBuffer buffer = response.bufferFactory().wrap(bytes);
            return response.writeWith(Mono.just(buffer));
        }
    }

    private boolean validateTokenInRedis(Long userId, String jwtToken) {
        String redisToken = redisUtils.getUserToken(userId);
        log.info("Checking Redis token for user {}: {}", userId, redisToken != null ? "Found" : "Not found");

        if (jwtToken.equals(redisToken)) {
            log.info("Token found in Redis cache for user {}", userId);
            return true;
        } else if (redisToken != null) {
            log.info("Token mismatch in Redis cache for user {}", userId);
            return false;
        }

        // Redis中不存在，通过Feign客户端调用user-app服务验证Token
        log.info("Token not in Redis, validating via Feign client for user {}", userId);
        try {
            boolean isValid = userFeignClient.validateToken(userId, jwtToken);
            log.info("Token validation via Feign result: {}", isValid);

            if (isValid) {
                // 验证成功，将Token存入Redis缓存，提高后续访问性能
                redisUtils.setUserToken(userId, jwtToken);
                log.info("Token verified by user service, cached in Redis for user {}", userId);
            }

            return isValid;
        } catch (Exception e) {
            log.error("Error validating token via Feign: {}", e.getMessage(), e);

            // Feign调用失败时，仍尝试JWT基本验证作为降级策略
            log.info("Feign call failed, falling back to JWT validation for user {}", userId);
            boolean isValid = jwtUtils.validateToken(jwtToken);

            if (isValid) {
                // 临时添加到Redis
                redisUtils.setUserToken(userId, jwtToken);
                log.info("JWT validation succeeded as fallback for user {}", userId);
            }

            return isValid;
        }
    }
} 